/**
 * @file Feature.c
 * @author 申飞麟
 * @brief 管理功能实现函数
 * @date 2025.2.23
 */

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>
#include "UI.h"
#include "Feature.h"
#include <assert.h>
#include <unistd.h>

/**
 * @brief 宏函数，用于判断输入的数字是否在1~4之间
 * @param Keyboard 键盘输入的值
 */
#define Select_Mode(Keyboard)                       \
    do                                              \
    {                                               \
        while (Keyboard > 5 || Keyboard <= 0)       \
        {                                           \
            printf("\n输入数字有误, 请重新输入: "); \
            scanf("%" SCNd32 "", &Keyboard);        \
        }                                           \
    } while (0)

static void *Overall = NULL; ///< 只在Feature.c内部使用的全局指针，用于查询或修改各类别

/**
 * @brief 查询学生信息函数
 * @param Read_Fp 读文件的指针
 * @param Information 学生信息指针
 * @param Keyboard 键盘输入的值
 * @return 正常返回 FEATURE_NOERROR，文件打开失败返回 FEATURE_ERROR_FILE
 */
Status Find_Information(FILE *Read_Fp, Student_Information *Information, const uint8_t Keyboard)
{
    int8_t Common[12] = " ";                 ///< 中间变量，用于暂存各种指针的值
    bool Room_Mode = false;                  ///< 查询宿舍号时改变查询方式的变量
    Read_Fp = fopen("Information.txt", "r"); // 以只读方式打开文件 Information.txt
    if (Read_Fp == NULL)                     // 判断文件是否打开成功
    {
        fprintf(stderr, "文件打开失败！\n");
        return FEATURE_ERROR_FILE;
    }
    switch (Keyboard) // 根据键入值选择查询方式，同时调用全局指针 Overall
    {
    case 1:
        printf("\n请输入姓名: ");
        Overall = Information->Name;
        break;
    case 2:
        printf("\n请输入学号(11位): ");
        Overall = Information->ID;
        break;
    case 3:
        printf("\n请输入电话(11位): ");
        Overall = Information->Phone;
        break;
    case 4:
        printf("\n请输入宿舍号(4位): ");
        Overall = Information->Room;
        break;
    }
    scanf("%s", Common);

    while (strlen(Common) > 11 || Overall == Information->Room && strlen(Common) > 4) // 判断输入值是否合规
    {
        printf("\n输入过长, 请重新输入:");
        rewind(stdin);
        scanf("%s", Common);
    }
    printf("\n");

    for (int8_t i = 0; i < 36; i++) // 调用文件指针Read_Fp，依次将 Information.txt 中的值存入学生信息指针 Information 中
    {
        fscanf(Read_Fp, "%s", Information->Name);
        fscanf(Read_Fp, "%s", Information->ID);
        fscanf(Read_Fp, "%s", Information->Phone);
        fscanf(Read_Fp, "%s", Information->Room);
        if (strcmp(Common, (char *)Overall) == 0 && !Room_Mode) // 判断是否查到及是否处于查找宿舍号模式
        {
            printf("查找成功,信息如下:\n\n");
            printf("姓名\t学号\t\t电话\t\t宿舍号\n\n");
            printf("%s\t%s\t%s\t%s\n", Information->Name, Information->ID, Information->Phone, Information->Room); // 打印信息指针中的信息
            if ((char *)Overall != Information->Room)                                                              // 判断成功则说明已查到，且不是查找宿舍号
            {
                printf("\n");
                system("pause");
                fclose(Read_Fp);
                Overall = NULL;
                return FEATURE_NOERROR;
            }
            Room_Mode = true; // 表示已查到，但查找宿舍号，需要多次查找
        }
        else if (Room_Mode && strcmp(Common, (char *)Overall) == 0) // 查宿舍号时打印已查到的信息
        {
            printf("%s\t%s\t%s\t%s\n", Information->Name, Information->ID, Information->Phone, Information->Room);
        }
    }
    if (!Room_Mode) // 判断是否查找到信息
    {
        printf("查找不到信息!\n");
    }
    printf("\n");
    system("pause");
    fclose(Read_Fp); // 关闭文件
    Overall = NULL;  // 恢复全局指针
    return FEATURE_NOERROR;
}

/**
 * @brief 链表初始化函数，只在点名时使用
 * @return 正常返回链表指针，内存分配失败则立即终止程序
 */
List_CallName *List_Init(void)
{
    List_CallName *Example = (List_CallName *)malloc(sizeof(List_CallName)); // 链表头初始化
    assert(Example != NULL);
    Example->Name = (uint8_t *)malloc(10 * sizeof(uint8_t)); // 名字初始化
    if (Example->Name == NULL)
    {
        free(Example); // 清除内存，避免泄露
        assert(0);
    }
    strcpy(Example->Name, "Head"); // 头部数据为 Head
    Example->Next = NULL;          // 初始化头部指针
    return Example;
}

/**
 * @brief 链表增加函数（尾部增加）
 * @param Example 链表头
 * @param InputName 输入的名字
 * @return 正常返回 FEATURE_NOERROR，内存分配失败则立即终止程序
 */
Status List_Add(List_CallName *Example, uint8_t *InputName)
{
    List_CallName *Find = Example; // 建立查找指针
    while (Find->Next != NULL)     // 查找当前链表是否为尾部
    {
        Find = Find->Next;
    }
    List_CallName *Create = (List_CallName *)malloc(sizeof(List_CallName)); // 新增链表
    assert(Create != NULL);
    Create->Name = (uint8_t *)malloc(15 * sizeof(uint8_t)); // 新增名字
    if (Example->Name == NULL)
    {
        free(Example); // 清除内存，避免泄露
        assert(0);
    }
    strcpy(Create->Name, InputName); // 导入名字
    Find->Next = Create;             // 连接链表头尾
    Create->Next = NULL;
    return FEATURE_NOERROR;
}

/**
 * @brief 链表查找函数
 * @param Example 链表头
 * @param Number 序号
 * @return 返回找到的名字
 */
uint8_t *List_Find(List_CallName *Example, uint8_t Number)
{
    uint8_t Sum = 0;               ///< 指针移动次数
    List_CallName *Find = Example; ///< 建立查找指针
    while (Sum < Number)
    {
        Find = Find->Next;
        Sum++;
    }
    return Find->Name;
}

/**
 * @brief 链表清除函数
 * @param Example 链表头
 * @return 链表清零
 */
void List_Delete(List_CallName *Example)
{
    List_CallName *MoveSlow = Example; ///< 用于清零链表元素
    List_CallName *MoveFast = NULL;    ///< 用于指向脱链部分
    while (MoveSlow->Next != NULL)     // 提前获取下一链的信息，防止野指针
    {
        MoveFast = MoveSlow->Next;
        MoveSlow->Next = NULL;
        free(MoveSlow->Name);
        MoveSlow->Name = NULL;
        free(MoveSlow);
        MoveSlow = MoveFast;
    }
    free(MoveSlow->Name);
    MoveSlow->Name = NULL;
    free(MoveSlow);
}

/**
 * @brief 点名与统计函数
 * @param Read_Fp 读文件的指针
 * @param Example 链表头
 * @param Information 学生信息指针
 * @param Keyboard 键盘输入的值
 * @return 正常返回 FEATURE_NOERROR，文件打开失败返回 FEATURE_ERROR_FILE
 */
Status Call_Name(FILE *Read_Fp, List_CallName *Example, Student_Information *Information, uint8_t Keyboard)
{
    uint8_t Call_Remark[50] = " ";               // 点名后暂存备注的字符串
    bool Call_Mode = false;                      // 开始点名模式切换
    FILE *Call_Fp = fopen("Call_Name.txt", "a"); ///< 将点名内容写入文件 Call_Name.txt
    Read_Fp = fopen("Information.txt", "r");     // 读取文件 Information.txt
    if (Read_Fp == NULL)                         // 判断文件是否打开成功
    {
        fprintf(stderr, "文件打开失败！\n");
        return FEATURE_ERROR_FILE;
    }
    if (Call_Fp == NULL)
    {
        fprintf(stderr, "文件打开失败！\n");
        return FEATURE_ERROR_FILE;
    }
    Example = List_Init(); // 创建点名链表

    while (Keyboard) // 循环读取信息，直到键入 0 时停下
    {
        int8_t Serial_Number = 1; ///< 链表序号
        printf("\n\n----------------------------------------------------------------\n");
        for (int8_t i = 0; i < 12; i++) // 依次扫描信息，读取并打印
        {
            for (int8_t k = 0; k < 3; k++)
            {
                fscanf(Read_Fp, "%s", Information->Name); // 依次扫描信息并存放至学生信息指针中，全部信息都读，但只有名字被复制到链表中
                fscanf(Read_Fp, "%s", Information->ID);
                fscanf(Read_Fp, "%s", Information->Phone);
                fscanf(Read_Fp, "%s", Information->Room);
                if (Call_Mode) // 判断是否开始点名，是则读取链表中的名字
                {
                    if (Serial_Number == Keyboard) // 判断键入数字是否和序号一致，一致则清空链表内相应的名字
                    {
                        strcpy(List_Find(Example, Serial_Number), "");
                    }
                    printf("%" PRId32 "\t%s\t", Serial_Number, List_Find(Example, Serial_Number)); // 打印链表中的名字
                }
                else // 判断是否开始点名，不是则读取学生信息指针中的名字
                {
                    printf("%" PRId32 "\t%s\t", Serial_Number, Information->Name); // 打印学生信息指针中的名字
                    List_Add(Example, Information->Name);                          // 创建链表元素并保存读取的名字
                }
                Serial_Number++;
            }
            printf("\n");
        }
        printf("----------------------------------------------------------------\n\n");
        rewind(Read_Fp); // 将读文件的指针移到开头
        printf("\n输入序号以点名, 点名结束请按 0 :\t");
        scanf("%" SCNd32 "", &Keyboard);
        Call_Mode = true; // 开始点名
    }
    for (int8_t i = 0; i < 36; i++)
    {
        if (strcmp(List_Find(Example, i + 1), "")) // 如果链表中的名字被清除，将不会写入文件 Call_Name.txt 中
        {
            fputs(List_Find(Example, i + 1), Call_Fp);
            fputs("\n", Call_Fp);
        }
    }
    printf("\n请为本次点名备注:");
    scanf("%s", Call_Remark);

    fputs("\n备注:\t", Call_Fp); // 将备注写入文件 Call_Name.txt 中
    fputs(Call_Remark, Call_Fp);
    fputs("\n-----------\n", Call_Fp);

    List_Delete(Example); // 清空链表
    fclose(Read_Fp);      // 关闭文件
    fclose(Call_Fp);
    printf("\n");
    system("pause");
    return FEATURE_NOERROR;
}

/**
 * @brief 修改学生信息函数
 * @param Read_Fp 读文件的指针
 * @param Information 学生信息指针
 * @param Keyboard 键盘输入的值
 * @return 正常返回 FEATURE_NOERROR，文件打开失败返回 FEATURE_ERROR_FILE
 */
Status Revise_Information(FILE *Read_Fp, Student_Information *Information, uint8_t Keyboard)
{
    int8_t Common[20] = " ";                 ///< 中间变量，用于暂存各种指针的值
    bool Revise_Mode = false;                ///< 判断是否进行修改的变量
    FILE *Revise_Fp = NULL;                  ///< 将修改的值写入文件 Change_Information.txt 中
    Read_Fp = fopen("Information.txt", "r"); // 以只读方式打开文件 Information.txt
    if (Read_Fp == NULL)                     // 判断文件是否打开成功
    {
        fprintf(stderr, "文件打开失败！\n");
        return FEATURE_ERROR_FILE;
    }
    Revise_Fp = fopen("Change_Information.txt", "a"); // 创建文件 Change_Information.txt 并准备写入
    if (Revise_Fp == NULL)
    {
        fprintf(stderr, "文件打开失败！\n");
        return FEATURE_ERROR_FILE;
    }
    switch (Keyboard) // 根据键入值选择修改方式，同时调用全局指针 Overall
    {
    case 1:
        printf("\n请输入姓名: ");
        Overall = Information->Name;
        break;
    case 2:
        printf("\n请输入学号(11位): ");
        Overall = Information->ID;
        break;
    case 3:
        printf("\n请输入电话(11位): ");
        Overall = Information->Phone;
        break;
    case 4:
        printf("\n请输入宿舍号(4位): ");
        Overall = Information->Room;
        break;
    }
    scanf("%s", Common);

    while (strlen(Common) > 11 || Overall == Information->Room && strlen(Common) > 4) // 判断输入值是否合规
    {
        printf("\n输入过长, 请重新输入:");
        rewind(stdin);
        scanf("%s", Common);
    }
    for (int8_t i = 0; i < 36; i++) // 调用文件指针Read_Fp，依次将 Information.txt 中的值存入学生信息指针 Information 中
    {
        fscanf(Read_Fp, "%s", Information->Name);
        fscanf(Read_Fp, "%s", Information->ID);
        fscanf(Read_Fp, "%s", Information->Phone);
        fscanf(Read_Fp, "%s", Information->Room);
        if (strcmp(Common, (char *)Overall) == 0 && !Revise_Mode) // 判断是否查找成功及修改是否结束
        {
            printf("\n查找成功,信息如下:\n\n");
            printf("姓名\t学号\t\t电话\t\t宿舍号\n\n");
            printf("%s\t%s\t%s\t%s\n", Information->Name, Information->ID, Information->Phone, Information->Room); // 打印成功查找的信息
            if (Overall == Information->Room)                                                                      // 判断是否为修改宿舍号方式，是则询问是否修改，不是则开始修改
            {
                printf("\n这是你想找的学生吗? 不是请按 0 , 是请输入其它数:");
                scanf("%" SCNd32 "", &Keyboard);
            }
            if (Keyboard) // 开始修改
            {
                printf("\n请输入字符以修改: ");
                scanf("%s", Common);
                while (strlen(Common) > 11 || Overall == Information->Room && strlen(Common) > 4) // 判断输入值是否合规
                {
                    printf("\n输入过长, 请重新输入:");
                    rewind(stdin);
                    scanf("%s", Common);
                }
                strcpy(Overall, Common); // 将修改的值复制到学生信息指针中
                Revise_Mode = true;      // 修改结束
            }
        }
        fputs(Information->Name, Revise_Fp); // 未修改的数据复制到 Change_Information.txt 中
        fputs("\t", Revise_Fp);
        fputs(Information->ID, Revise_Fp);
        fputs("\t", Revise_Fp);
        fputs(Information->Phone, Revise_Fp);
        fputs("\t", Revise_Fp);
        fputs(Information->Room, Revise_Fp);
        fputs("\n", Revise_Fp);
    }
    if (Revise_Mode) // 判断是否进行了修改
    {
        printf("\n请输入本次修改日期: ");
        scanf("%s", Common);
        fputs("\n修改日期为:\t", Revise_Fp); // 添加修改日期
        fputs(Common, Revise_Fp);
    }
    else
    {
        printf("\n由于查询失败或宿舍号不符, 本次未进行修改!\n");
    }
    fclose(Revise_Fp); // 关闭文件
    fclose(Read_Fp);

    remove("Information.txt"); // 删除 Information.txt，把 Change_Information.txt 更名为 Information.txt
    rename("Change_Information.txt", "Information.txt");
    Overall = NULL; // 恢复全局指针
    printf("\n");
    system("pause");
    return FEATURE_ERROR_NOFIND;
}

/**
 * @brief 系统控制面板函数，用来跳转至各个模块
 * @return 正常返回 FEATURE_NOERROR，终止程序返回 FEATURE_EXIT，键入值不正确返回 FEATURE_ERROR_NOFIND
 */
Status Manage_Panel(void)
{
    Student_Manage Manage_System; ///< 学生系统样板,包括信息和函数指针
    Manage_System.Call_Name = Call_Name;
    Manage_System.Find_Information = Find_Information;
    Manage_System.Revise_Information = Revise_Information;
    List_CallName Example;         ///< 点名链表
    FILE *Information_File = NULL; ///< 读文件的指针
    uint8_t Keyboard = 0;          ///< 键入值

    UI_Home(); // 加载主页
    scanf("%d", &Keyboard);
    switch (Keyboard) // 各个模块跳转
    {
    case 1: // 进入查询学生信息模块
        UI_Find_Information();
        scanf("%" SCNd8 "", &Keyboard);
        Select_Mode(Keyboard);
        if (Keyboard != 5) // 判断是否返回至主页
        {
            Manage_System.Find_Information(Information_File, &Manage_System.Base, Keyboard);
        }
        break;
    case 2: // 进入点名与统计模块，并等待 10 秒
        UI_Call();
        sleep(10);
        Call_Name(Information_File, &Example, &Manage_System.Base, Keyboard);
        break;
    case 3: // 进入修改学生信息模块
        UI_Revise_Information();
        scanf("%d", &Keyboard);
        Select_Mode(Keyboard);
        if (Keyboard != 5) // 判断是否返回至主页
        {
            Revise_Information(Information_File, &Manage_System.Base, Keyboard);
        }
        break;
    case 4: // 退出程序
        return FEATURE_EXIT;
    default: // 键入值错误则等待 2 秒后返回至主页
        printf("\n输入有误,请稍后······\n");
        sleep(2);
        return FEATURE_ERROR_NOFIND;
    }
}

